/*
 * @(#) $Header$
 *
 * Copyright (C) 2010  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.crypto.md;

/**
 * This class embodies the algorithm for the message digest MD2 according to
 * <a target="external" href="http://www.faqs.org/rfcs/rfc1319.html">RFC1319</a>.
 * It is more elegant to get an instance with the factory method in class
 * {@link java.security.MessageDigest} :
 * <blockquote>
 * <code>MessageDigest md2 = MessageDigest.getInstance(Forklabs.MD_2, Forklabs.NAME);</code>
 * </blockquote>
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.crypto.md.MD2">Daniel Léonard</a>
 * @version $Revision$
 * @see      ca.forklabs.crypto.Forklabs
 */
public class MD2 extends AbstractBlockMessageDigest {

//----------------------------
// Class variables
//----------------------------

   /** The name of this message digests. */
   @SuppressWarnings("nls")
   public static final String NAME = "MD2";

   /** The padding bytes. */
   private static final byte[][] PADDING = {
      {},
      {1},
      {2, 2},
      {3, 3, 3},
      {4, 4, 4, 4},
      {5, 5, 5, 5, 5},
      {6, 6, 6, 6, 6, 6},
      {7, 7, 7, 7, 7, 7, 7},
      {8, 8, 8, 8, 8, 8, 8, 8},
      {9, 9, 9, 9, 9, 9, 9, 9, 9},
      {10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
      {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11},
      {12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12},
      {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13},
      {14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14},
      {15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15},
      {16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}
      };

   /** The substitutions. */
   private static final int[] PI_SUBS = {
       41,  46,  67, 201, 162, 216, 124,   1,  61,  54,  84, 161, 236, 240,   6,  19,
       98, 167,   5, 243, 192, 199, 115, 140, 152, 147,  43, 217, 188,  76, 130, 202,
       30, 155,  87,  60, 253, 212, 224,  22, 103,  66, 111,  24, 138,  23, 229,  18,
      190,  78, 196, 214, 218, 158, 222,  73, 160, 251, 245, 142, 187,  47, 238, 122,
      169, 104, 121, 145,  21, 178,   7,  63, 148, 194,  16, 137,  11,  34,  95,  33,
      128, 127,  93, 154,  90, 144,  50,  39,  53,  62, 204, 231, 191, 247, 151,   3,
      255,  25,  48, 179,  72, 165, 181, 209, 215,  94, 146,  42, 172,  86, 170, 198,
       79, 184,  56, 210, 150, 164, 125, 182, 118, 252, 107, 226, 156, 116,   4, 241,
       69, 157, 112,  89, 100, 113, 135,  32, 134,  91, 207, 101, 230,  45, 168,   2,
       27,  96,  37, 173, 174, 176, 185, 246,  28,  70,  97, 105,  52,  64, 126,  15,
       85,  71, 163,  35, 221,  81, 175,  58, 195,  92, 249, 206, 186, 197, 234,  38,
       44,  83,  13, 110, 133,  40, 132,   9, 211, 223, 205, 244,  65, 129,  77,  82,
      106, 220,  55, 200, 108, 193, 171, 250,  36, 225, 123,   8,  12, 189, 177,  74,
      120, 136, 149, 139, 227,  99, 232, 109, 233, 203, 213, 254,  59,   0,  29,  57,
      242, 239, 183,  14, 102,  88, 208, 228, 166, 119, 114, 248, 235, 117,  75,  10,
       49,  68,  80, 180, 143, 237,  31,  26, 219, 153, 141,  51, 159,  17, 131,  20
      };

   /** The length in bytes of the digest. */
   private static final int DIGEST_LEN = 16;

   /** The length in bytes of a block. */
   private static final int BUFFER_LEN = 16;


//----------------------------
// Instance variables
//----------------------------

   /** The state (ABCD). */
   private byte[] state = new byte[MD2.DIGEST_LEN];

   /** The checksum. */
   private byte[] checksum = new byte[16];

   /** The scrambling buffer. */
   private int[] x = new int[48];


//----------------------------
// Constructors
//----------------------------

   /**
    * Constructor.
    */
   public MD2() {
      super(MD2.NAME, MD2.BUFFER_LEN, MD2.DIGEST_LEN);
      this.coreInit();
      }


//----------------------------
// Accessors
//----------------------------

   /**
    * Gets the state buffer.
    * @return   the state buffer.
    */
   protected byte[] getState() {
      return this.state;
      }

   /**
    * Gets the checksum buffer.
    * @return  the checksum buffer.
    */
   protected byte[] getChecksum() {
      return this.checksum;
      }

   /**
    * Gets the scrambling buffer x.
    * @return   the scrambling buffer.
    */
   protected int[] getScramblingBuffer() {
      return this.x;
      }


//----------------------------
// Concrete core methods
//----------------------------

   @Override
   @SuppressWarnings("hiding")
   protected void coreTransform() {
      byte[] state = this.getState();
      int[] x = this.getScramblingBuffer();
   // first transfer state
      x[ 0] = state[ 0]  & 0xff;
      x[ 1] = state[ 1]  & 0xff;
      x[ 2] = state[ 2]  & 0xff;
      x[ 3] = state[ 3]  & 0xff;
      x[ 4] = state[ 4]  & 0xff;
      x[ 5] = state[ 5]  & 0xff;
      x[ 6] = state[ 6]  & 0xff;
      x[ 7] = state[ 7]  & 0xff;
      x[ 8] = state[ 8]  & 0xff;
      x[ 9] = state[ 9]  & 0xff;
      x[10] = state[10]  & 0xff;
      x[11] = state[11]  & 0xff;
      x[12] = state[12]  & 0xff;
      x[13] = state[13]  & 0xff;
      x[14] = state[14]  & 0xff;
      x[15] = state[15]  & 0xff;

   // then the block
      byte[] buffer = this.getBuffer();
      x[16] = buffer[ 0] & 0xff;
      x[17] = buffer[ 1] & 0xff;
      x[18] = buffer[ 2] & 0xff;
      x[19] = buffer[ 3] & 0xff;
      x[20] = buffer[ 4] & 0xff;
      x[21] = buffer[ 5] & 0xff;
      x[22] = buffer[ 6] & 0xff;
      x[23] = buffer[ 7] & 0xff;
      x[24] = buffer[ 8] & 0xff;
      x[25] = buffer[ 9] & 0xff;
      x[26] = buffer[10] & 0xff;
      x[27] = buffer[11] & 0xff;
      x[28] = buffer[12] & 0xff;
      x[29] = buffer[13] & 0xff;
      x[30] = buffer[14] & 0xff;
      x[31] = buffer[15] & 0xff;

   // finally the mix
      x[32] = x[ 0] ^ x[16];
      x[33] = x[ 1] ^ x[17];
      x[34] = x[ 2] ^ x[18];
      x[35] = x[ 3] ^ x[19];
      x[36] = x[ 4] ^ x[20];
      x[37] = x[ 5] ^ x[21];
      x[38] = x[ 6] ^ x[22];
      x[39] = x[ 7] ^ x[23];
      x[40] = x[ 8] ^ x[24];
      x[41] = x[ 9] ^ x[25];
      x[42] = x[10] ^ x[26];
      x[43] = x[11] ^ x[27];
      x[44] = x[12] ^ x[28];
      x[45] = x[13] ^ x[29];
      x[46] = x[14] ^ x[30];
      x[47] = x[15] ^ x[31];

   // 17 rounds of hashin
   // see the comment at the end of the loop for why 17 instead of 18
      int t = 0;
      for (int i = 0; i < 17; i++) {
         x[ 0] ^= MD2.PI_SUBS[t];
         x[ 1] ^= MD2.PI_SUBS[x[ 0]];
         x[ 2] ^= MD2.PI_SUBS[x[ 1]];
         x[ 3] ^= MD2.PI_SUBS[x[ 2]];
         x[ 4] ^= MD2.PI_SUBS[x[ 3]];
         x[ 5] ^= MD2.PI_SUBS[x[ 4]];
         x[ 6] ^= MD2.PI_SUBS[x[ 5]];
         x[ 7] ^= MD2.PI_SUBS[x[ 6]];
         x[ 8] ^= MD2.PI_SUBS[x[ 7]];
         x[ 9] ^= MD2.PI_SUBS[x[ 8]];
         x[10] ^= MD2.PI_SUBS[x[ 9]];
         x[11] ^= MD2.PI_SUBS[x[10]];
         x[12] ^= MD2.PI_SUBS[x[11]];
         x[13] ^= MD2.PI_SUBS[x[12]];
         x[14] ^= MD2.PI_SUBS[x[13]];
         x[15] ^= MD2.PI_SUBS[x[14]];
         x[16] ^= MD2.PI_SUBS[x[15]];
         x[17] ^= MD2.PI_SUBS[x[16]];
         x[18] ^= MD2.PI_SUBS[x[17]];
         x[19] ^= MD2.PI_SUBS[x[18]];
         x[20] ^= MD2.PI_SUBS[x[19]];
         x[21] ^= MD2.PI_SUBS[x[20]];
         x[22] ^= MD2.PI_SUBS[x[21]];
         x[23] ^= MD2.PI_SUBS[x[22]];
         x[24] ^= MD2.PI_SUBS[x[23]];
         x[25] ^= MD2.PI_SUBS[x[24]];
         x[26] ^= MD2.PI_SUBS[x[25]];
         x[27] ^= MD2.PI_SUBS[x[26]];
         x[28] ^= MD2.PI_SUBS[x[27]];
         x[29] ^= MD2.PI_SUBS[x[28]];
         x[30] ^= MD2.PI_SUBS[x[29]];
         x[31] ^= MD2.PI_SUBS[x[30]];
         x[32] ^= MD2.PI_SUBS[x[31]];
         x[33] ^= MD2.PI_SUBS[x[32]];
         x[34] ^= MD2.PI_SUBS[x[33]];
         x[35] ^= MD2.PI_SUBS[x[34]];
         x[36] ^= MD2.PI_SUBS[x[35]];
         x[37] ^= MD2.PI_SUBS[x[36]];
         x[38] ^= MD2.PI_SUBS[x[37]];
         x[39] ^= MD2.PI_SUBS[x[38]];
         x[40] ^= MD2.PI_SUBS[x[39]];
         x[41] ^= MD2.PI_SUBS[x[40]];
         x[42] ^= MD2.PI_SUBS[x[41]];
         x[43] ^= MD2.PI_SUBS[x[42]];
         x[44] ^= MD2.PI_SUBS[x[43]];
         x[45] ^= MD2.PI_SUBS[x[44]];
         x[46] ^= MD2.PI_SUBS[x[45]];
         x[47] ^= MD2.PI_SUBS[x[46]];
         t = (x[47] + i) % 256;
         }
   // final iteration : the last 32 operations are useless since we save
   // the 16 first bytes. This is not like SNEFRU which takes the last
   // bytes. That could make a nice paper :)

   // and save new state
      state[ 0] = (byte) (x[ 0] ^= MD2.PI_SUBS[t]         );
      state[ 1] = (byte) (x[ 1] ^= MD2.PI_SUBS[x[ 0]]);
      state[ 2] = (byte) (x[ 2] ^= MD2.PI_SUBS[x[ 1]]);
      state[ 3] = (byte) (x[ 3] ^= MD2.PI_SUBS[x[ 2]]);
      state[ 4] = (byte) (x[ 4] ^= MD2.PI_SUBS[x[ 3]]);
      state[ 5] = (byte) (x[ 5] ^= MD2.PI_SUBS[x[ 4]]);
      state[ 6] = (byte) (x[ 6] ^= MD2.PI_SUBS[x[ 5]]);
      state[ 7] = (byte) (x[ 7] ^= MD2.PI_SUBS[x[ 6]]);
      state[ 8] = (byte) (x[ 8] ^= MD2.PI_SUBS[x[ 7]]);
      state[ 9] = (byte) (x[ 9] ^= MD2.PI_SUBS[x[ 8]]);
      state[10] = (byte) (x[10] ^= MD2.PI_SUBS[x[ 9]]);
      state[11] = (byte) (x[11] ^= MD2.PI_SUBS[x[10]]);
      state[12] = (byte) (x[12] ^= MD2.PI_SUBS[x[11]]);
      state[13] = (byte) (x[13] ^= MD2.PI_SUBS[x[12]]);
      state[14] = (byte) (x[14] ^= MD2.PI_SUBS[x[13]]);
      state[15] = (byte) (x[15] ^= MD2.PI_SUBS[x[14]]);

   // update checksum
      byte[] checksum = this.getChecksum();
      checksum[ 0] ^= MD2.PI_SUBS[(buffer[ 0] ^ checksum[15]) & 0xff];
      checksum[ 1] ^= MD2.PI_SUBS[(buffer[ 1] ^ checksum[ 0]) & 0xff];
      checksum[ 2] ^= MD2.PI_SUBS[(buffer[ 2] ^ checksum[ 1]) & 0xff];
      checksum[ 3] ^= MD2.PI_SUBS[(buffer[ 3] ^ checksum[ 2]) & 0xff];
      checksum[ 4] ^= MD2.PI_SUBS[(buffer[ 4] ^ checksum[ 3]) & 0xff];
      checksum[ 5] ^= MD2.PI_SUBS[(buffer[ 5] ^ checksum[ 4]) & 0xff];
      checksum[ 6] ^= MD2.PI_SUBS[(buffer[ 6] ^ checksum[ 5]) & 0xff];
      checksum[ 7] ^= MD2.PI_SUBS[(buffer[ 7] ^ checksum[ 6]) & 0xff];
      checksum[ 8] ^= MD2.PI_SUBS[(buffer[ 8] ^ checksum[ 7]) & 0xff];
      checksum[ 9] ^= MD2.PI_SUBS[(buffer[ 9] ^ checksum[ 8]) & 0xff];
      checksum[10] ^= MD2.PI_SUBS[(buffer[10] ^ checksum[ 9]) & 0xff];
      checksum[11] ^= MD2.PI_SUBS[(buffer[11] ^ checksum[10]) & 0xff];
      checksum[12] ^= MD2.PI_SUBS[(buffer[12] ^ checksum[11]) & 0xff];
      checksum[13] ^= MD2.PI_SUBS[(buffer[13] ^ checksum[12]) & 0xff];
      checksum[14] ^= MD2.PI_SUBS[(buffer[14] ^ checksum[13]) & 0xff];
      checksum[15] ^= MD2.PI_SUBS[(buffer[15] ^ checksum[14]) & 0xff];

   // zeroize sensitive information.
      AbstractBlockMessageDigest.zeroize(x);
      AbstractBlockMessageDigest.zeroize(buffer);
      }

   @Override
   @SuppressWarnings("hiding")
   protected void coreInit() {
      int[] x = this.getScramblingBuffer();
      AbstractBlockMessageDigest.zeroize(x);

      byte[] state = this.getState();
      AbstractBlockMessageDigest.zeroize(state);

      byte[] checksum = this.getChecksum();
      AbstractBlockMessageDigest.zeroize(checksum);

      byte[] buffer = this.getBuffer();
      AbstractBlockMessageDigest.zeroize(buffer);
      }

   @Override
   @SuppressWarnings("hiding")
   protected byte[] coreDoFinal() {
      long count = this.getCount();
      byte[] pad = MD2.PADDING[(int) (16L - (count % 16L))];
      this.engineUpdate(pad, 0, pad.length);

      byte[] checksum = this.getChecksum();
      this.engineUpdate(checksum, 0, 16);

      byte[] state = this.getState();
      byte[] digest = new byte[MD2.DIGEST_LEN];
      System.arraycopy(state, 0, digest, 0, 16);

      this.coreInit();

      return digest;
      }


//---------------------------
// Implemented method from java.lang.Cloneable
//---------------------------

   /**
    * Makes a deep copy of this message digest in its current state.
    * @return   a clone of this message digest instance.
    */
   @Override
   public Object clone() throws CloneNotSupportedException {
      MD2 md2 = (MD2) super.clone();
      md2.state = this.state.clone();
      md2.checksum = this.checksum.clone();
      md2.x = this.x.clone();
      return md2;
      }

   }

